package cn.kk.fluidcells.cells.keyboard.internal;

import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.WinDef.HMODULE;
import com.sun.jna.platform.win32.WinUser;
import com.sun.jna.platform.win32.WinUser.LowLevelKeyboardProc;
import com.sun.jna.platform.win32.WinUser.MSG;

// mouse and keyboard recorder, ghost logger ghost recorder
public class KeyboardHook implements MediaCodeSupport {
    private boolean started = false;
    private User32 lib;
    private HMODULE moduleHandle;

    public static com.sun.jna.platform.win32.User32.HHOOK keyboardHook;

    public KeyboardHook() {
        this.lib = com.sun.jna.platform.win32.User32.INSTANCE;
        this.moduleHandle = Kernel32.INSTANCE.GetModuleHandle(null);
    }

    private final static int setModifierMask(int mods, int mask) {
        return mods | mask;
    }

    private final static int unsetModifierMask(int mods, int mask) {
        return mods & (~mask);
    }

    public static final boolean isModifierKey(final int rawCode) {
        switch (rawCode) {
        case MediaCodeSupport.VK_LSHIFT:
        case MediaCodeSupport.VK_RSHIFT:
        case MediaCodeSupport.VK_LCONTROL:
        case MediaCodeSupport.VK_RCONTROL:
        case MediaCodeSupport.VK_LMENU:
        case MediaCodeSupport.VK_RMENU:
        case MediaCodeSupport.VK_LWIN:
        case MediaCodeSupport.VK_RWIN:
            return true;
        default:
            return false;
        }
    }

    private LowLevelKeyboardProc keyboardProc;

    public void init() {
        this.keyboardProc = new DefaultLowLevelKeyboardProc();
    }

    public void stop() {
        if (started) {
            if (lib.UnhookWindowsHookEx(KeyboardHook.keyboardHook)) {
                System.out.println("keyboard: unhook successful");
            } else {
                System.err.println("keyboard: unhook failed");
            }
            started = false;
        }
    }

    public void start() {
        if (!started) {
            started = true;
            KeyboardHook.keyboardHook = lib.SetWindowsHookEx(WinUser.WH_KEYBOARD_LL, KeyboardHook.this.keyboardProc,
                    moduleHandle, 0);

            if (KeyboardHook.keyboardHook == null) {
                System.err.println("keyboard: hook failed");
                throw new RuntimeException("keyboard: hook failed");
            } else {
                System.out.println("keyboard: hooked");
                new Thread() {
                    public void run() {
                        MSG msg = new MSG();
                        lib.GetMessage(msg, null, 0, 0);
                    }
                }.start();
            }
        }
    }

    public static void main(String[] args) throws Exception {
        KeyboardHook hook = new KeyboardHook();
        hook.init();
        hook.start();
    }

    public static final int getModifiers(final int rawCode) {
        int m = 0;
        m = KeyboardHook.updateModifier(m, MediaCodeSupport.VK_LSHIFT, MediaCodeSupport.MOD_LSHIFT);
        m = KeyboardHook.updateModifier(m, MediaCodeSupport.VK_RSHIFT, MediaCodeSupport.MOD_RSHIFT);
        m = KeyboardHook.updateModifier(m, MediaCodeSupport.VK_LCONTROL, MediaCodeSupport.MOD_LCONTROL);
        m = KeyboardHook.updateModifier(m, MediaCodeSupport.VK_RCONTROL, MediaCodeSupport.MOD_RCONTROL);
        m = KeyboardHook.updateModifier(m, MediaCodeSupport.VK_LMENU, MediaCodeSupport.MOD_LALT);
        m = KeyboardHook.updateModifier(m, MediaCodeSupport.VK_RMENU, MediaCodeSupport.MOD_RALT);
        m = KeyboardHook.updateModifier(m, MediaCodeSupport.VK_LWIN, MediaCodeSupport.MOD_LWIN);
        m = KeyboardHook.updateModifier(m, MediaCodeSupport.VK_RWIN, MediaCodeSupport.MOD_RWIN);
        return m;
    }

    private static final int updateModifier(final int mods, final int key, final int modKey) {
        if (0x8000 == (0x8000 & User32.INSTANCE.GetAsyncKeyState(key))) {
            return KeyboardHook.setModifierMask(mods, modKey);
        } else {
            return KeyboardHook.unsetModifierMask(mods, modKey);
        }
    }

    public static final void printModifiers(final int modifiers) {
        // JHGFFFDFSSystem.out.println(Integer.toBinaryString(modifiers));
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < MediaCodeSupport.MODS.length; i++) {
            final int m = MediaCodeSupport.MODS[i];
            if ((m & modifiers) == m) {
                sb.append(MediaCodeSupport.MODS_TEXT[i]).append(", ");
            }
        }
        if (sb.length() > 0) {
            System.out.println(sb.toString());
        }
    }
}
